/*
* Copyright (c) 2014 Qunar.com. All Rights Reserved.
*/
package net.oschina.bilbodai.common.beanutil.property;

import net.oschina.bilbodai.common.beanutil.core.ASMCodeGenerator;
import net.oschina.bilbodai.common.beanutil.property.asm.lexer.code.CodeAppliers;
import net.oschina.bilbodai.common.beanutil.property.asm.lexer.code.prime.PrimesAppliers;
import net.oschina.bilbodai.common.beanutil.property.asm.lexer.parse.ITokenIterator;
import net.oschina.bilbodai.common.beanutil.property.asm.lexer.parse.StringTokenizerIterator;
import net.oschina.bilbodai.common.beanutil.property.asm.lexer.parse.Token;
import net.oschina.bilbodai.common.beanutil.property.asm.lexer.parse.TokenApplyCtx;
import net.oschina.bilbodai.common.beanutil.runtime.IRuntimeClassLoader;
import net.oschina.bilbodai.common.beanutil.runtime.RuntimeClassLoader;
import net.oschina.bilbodai.common.beanutil.util.TypeUtil;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.Closeable;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.FutureTask;

import static org.objectweb.asm.Opcodes.ACC_PUBLIC;
import static org.objectweb.asm.Opcodes.ACONST_NULL;
import static org.objectweb.asm.Opcodes.ALOAD;
import static org.objectweb.asm.Opcodes.ARETURN;

/**
 * 每个独立的属性路径都会生成一个class，比如 x.y.z 和 x.y.k
 * 同时对于list和array，每个获取的位置也会产生一个，比如 x.y[0] 和 x.y[1]
 * 对map来说，每个key也会, 比如 x.key1 和 x.key2
 * 所以这是一个很明显的缺点，会增加class的内存开销
 * 但这个问题可以通过缓存和失效来控制
 * 如果需要控制加载累的数量{@link #cacheProperties}
 * <pre>
 * {@link StringTokenizerIterator} {@code ->} {@link Token} {@code ->} {@link CodeAppliers}
 * </pre>
 *
 * @author qt-maodai  Date: 15-8-8 Time: 下午1:12
 * @version $Id$
 */
public class PropertyUtil implements Closeable {

    private static final Logger LOGGER = LoggerFactory.getLogger(PropertyUtil.class);

    public static final String PROPERTY_UTIL_PREFIX = "PropertyUtil$";
    private static IRuntimeClassLoader classLoader;
    private static PrintWriter printWriter;

    static {
        classLoader = AccessController.doPrivileged(new PrivilegedAction<IRuntimeClassLoader>() {

            public IRuntimeClassLoader run() {
                return new RuntimeClassLoader(PropertyUtil.class.getClassLoader());
            }

        });
    }

    private final static ConcurrentHashMap<String/* path expression */, FutureTask<Property>> cacheProperties =
            new ConcurrentHashMap<String, FutureTask<Property>>();

    public static Object getProperty(final String propertyPath, final Object bean) {
        return getProperty(propertyPath, bean, classLoader);
    }

    /**
     * 根据实例所在的属性路径字符串获取实例中属性值
     *
     * @param propertyPath 属性路径，类似 user.books[0].name
     * @param bean         实例，如 user
     * @return user第一本书的名字，注意如果中途某个属性为null，将抛出空指针异常，另外可能会抛出数组越界异常
     */
    public static Object getProperty(final String propertyPath, final Object bean, IRuntimeClassLoader classLoader) {
        if (propertyPath == null || propertyPath.trim().length() == 0) {
            throw new IllegalArgumentException("propertyPath can't be null, bean=" + bean);
        }
        if (bean == null) {
            //            throw new IllegalArgumentException("Bean Context can't be null, propertyPath=" + propertyPath);
            LOGGER.warn("Bean Context null, propertyPath=" + propertyPath);
            return null;
        }
        return create(propertyPath, bean.getClass(), classLoader).get(bean);

    }

    public static Property create(final String propertyPath, final Class<?> beanType){
        return create(propertyPath, beanType, classLoader);
    }
    public static Property create(final String propertyPath, final Class<?> beanType, final IRuntimeClassLoader classLoader) {
        final String cacheKey = beanType.getName() + "." + propertyPath;

        FutureTask<Property> task = cacheProperties.get(cacheKey);
        if (task != null) {
            return getCachedProperty(cacheKey, task);
        }
        task = new FutureTask<>(new Callable<Property>() {
            public Property call() throws Exception {
                return make(propertyPath, beanType, classLoader);
            }

        });
        FutureTask<Property> existedTask = cacheProperties.putIfAbsent(cacheKey, task);
        if (existedTask == null) {
            existedTask = task;
            existedTask.run();
        }
        return getCachedProperty(cacheKey, existedTask);
    }

    public static Property reCreate(final String propertyPath, final Class<?> beanType, IRuntimeClassLoader classLoader) {
        final String cacheKey = beanType.getName() + "." + propertyPath;
        cacheProperties.remove(cacheKey);
        return create(propertyPath, beanType, classLoader);
    }

    private static Property make(String propertyPath, final Class<?> beanType, IRuntimeClassLoader classLoader) throws Exception {
        StringTokenizerIterator stringTokenizerIterator = new StringTokenizerIterator(propertyPath);
        final String clsName = PROPERTY_UTIL_PREFIX + TypeUtil.getBeanUtilsClassSimpleName(beanType);
        ASMCodeGenerator asmCodeGenerator = new ASMCodeGenerator(clsName, Opcodes.V1_7, printWriter, Object.class, Property.class) {
            @Override
            public void onVisitorMethods(ClassVisitor cw, Object... args) throws Exception {
                ITokenIterator iterator = (ITokenIterator) args[0];
                Class<?> rootBeanType = (Class<?>) args[1];
                MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "get", "(Ljava/lang/Object;)Ljava/lang/Object;", null, null);
                mv.visitCode();

                Label retNullLabel = new Label();
                TokenApplyCtx applyCtx = new TokenApplyCtx(retNullLabel);

                mv.visitVarInsn(ALOAD, 1);
                Class<?> retType = new Token().apply(mv, iterator, rootBeanType, applyCtx);
                if (PrimesAppliers.isPrimeType(retType)) {
                    PrimesAppliers.applyCode(retType, mv);
                }
                mv.visitInsn(ARETURN);

                mv.visitLabel(retNullLabel);
                if (PrimesAppliers.isPrimeType(retType)) {
                    PrimesAppliers.BoxType boxType = PrimesAppliers.findBoxType(retType);
                    mv.visitInsn(boxType.getDefaultTypeLoadOpcode());
                    PrimesAppliers.applyCode(retType, mv);
                    mv.visitInsn(ARETURN);
                } else {
                    mv.visitInsn(ACONST_NULL);
                    mv.visitInsn(ARETURN);
                }

                mv.visitMaxs(0, 0);
                mv.visitEnd();
            }
        };
        Class<?> generateClass = asmCodeGenerator.getLoadedClass(classLoader, stringTokenizerIterator, beanType);
        return (Property) generateClass.newInstance();
    }

    private static Property getCachedProperty(final String cacheKey, FutureTask<Property> task) {
        try {
            return task.get();
        } catch (Exception e) {
            cacheProperties.remove(cacheKey);
            throw new IllegalStateException("获取路径为" + cacheKey + "的属性值出错", e);
        }
    }

    /**
     * 如果要打印class生成的日志，可以设置相应的输出流
     *
     * @param out 输出流
     */
    public static void setTrace(OutputStream out) {
        printWriter = new PrintWriter(new OutputStreamWriter(out));
    }

    public void close() throws IOException {
        if (printWriter != null) {
            printWriter.close();
        }
    }
}